const { createServer } = require('net')
const { notEqual, equal } = require('assert');
const { createHash } = require('crypto');
const GUID = `258EAFA5-E914-47DA-95CA-C5AB0DC85B11`

class Socekt {
  constructor() {
  }

  sendReload () {
    if (this.socket)
      this.socket.write(encodeWsFrame("reload"))
  }

  async listen (port) {
    notEqual(port, Infinity, 'Port cannot be empty!')
    notEqual(port, NaN, 'Port cannot be empty!')
    equal(typeof port, 'number', 'port must be of type number!')
    return new Promise((resolved, rejected) => {
      this.server = createServer(socket => {
        this.socket = socket;
        socket.once('data', (buffer) => {
          const headers = headerToObject(buffer.toString())
          if (headers.upgrade !== 'websocket') {
            socket.end()
            return
          }
          const secWebSocketKey = createSecWebSocketKey(headers['sec-websocket-key'])
          const header = `HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: ${secWebSocketKey}\r\n\r\n` // 生成供前端校验用的请求头
          socket.write(header)
        })
      }).listen(port, '127.0.0.1')
      resolved(this)
      this.server.on('error', rejected)
    })
  }
}

const encodeWsFrame = (message) => {
  equal(typeof message, 'string')

  const payload = Buffer.from(message);
  const payloadLength = payload.length;

  const frame = [129];

  if (payloadLength < 126) {
    frame.push(payloadLength);
  } else if (payloadLength < 65536) {
    frame.push(126, payloadLength >> 8, payloadLength & 0xFF);
  } else {
    frame.push(127);
    for (let i = 7; i >= 0; --i) {
      frame.push((payloadLength & (0xFF << (i * 8))) >> (i * 8));
    }
  }

  return Buffer.concat([Buffer.from(frame), payload]);
}

const createSecWebSocketKey = (key) => {
  equal(typeof key, 'string', 'key must be of type string!')
  const hash = createHash('sha1').update(`${key}${GUID}`)
  return hash.digest('base64')
}

const headerToObject = (headerData) => {
  equal(typeof headerData, 'string', 'header must be of type string!')
  headerData = headerData.split('\r\n').filter(item => item)
  headerData.shift()

  return headerData.reduce((headers, item) => {
    const [key, value] = item.split(':')
    headers[key.trim().toLocaleLowerCase()] = value.trim()
    return headers
  }, {})
}

module.exports = Socekt